/*
	klib_IRQ_control.c

	contains functionality for controlling IO APIC IRQs and ISRs

	Author: Aidan Goddard 5/10/13
*/

#include"headers/ktypes.h"
#include"headers/klib_IRQ_control.h"

#include"../kernel/headers/scheduler_functions.h"
#include"../kernel/headers/bakery_lock.h"
#include"../kernel/headers/kernel_tables.h"

// 64-bit IDT structure
typedef struct IDT64 IDT64;
struct __attribute__((__packed__)) IDT64
{
	uint16_t target_offset_low;
	uint16_t target_selector;
	uint8_t IST_selector : 3;
	uint8_t reserved_1 : 5;
	uint8_t type : 4;
	uint8_t zero : 1;
	uint8_t DPL : 2;
	uint8_t present : 1;
	uint16_t target_offset_mid;
	uint32_t target_offset_high;
	uint32_t reserved_2;
};

// internal lock structures
static SCH_CLS idt_lock;
static SCH_CLS sysconfig_lock;
static SCH_CLS ioapic_lock;


uint32_t	__KLIB__GetMSIDestination(uint32_t *buffer)
{
	// generate MSI Message Address Register value
	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// get CPU0 APIC ID
	uint32_t APIC = tbl->tbl_cpu[0].APIC_ID;

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	APIC = 0xFEE00000 | ((APIC & 0xff) << 12);
	*buffer = APIC;

	// finished
	return IRQ_SUCCESS;
}


uint32_t	__KLIB__ISRReserve(uint32_t priority, const uint32_t consec_count, ISR_VECTOR *vectors, uint64_t *keys)
{
	// check wanted priority is valid
	priority = priority << 4;
	if(priority < 32 || priority > 255)
	{
		return IRQ_BAD_PRIORITY_NUMBER;
	}

	// check count is valid
	uint32_t check_count = consec_count + priority;
	if(check_count > 256)
	{
		return IRQ_BAD_COUNT;
	}

	// have a look in the isr reservation table
	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// zero vectors array
	uint32_t i;
	for(i = 0; i < consec_count; i++)
	{
		vectors[i] = 0;
	}

	// start at the given priority level and move down until a block is available
	// if block of desired length is unavailable, don't reserve any
	uint32_t current_base, current_length;
	for(i = priority, current_base = 0, current_length = 0;; i++)
	{
		// check if we haven't gone over the end yet
		if(i > 255)
		{
			*vectors = 0;
			break;
		}

		// check if not in middle of consecutive array
		if(current_base)
		{
			// check this vector is available
			if(tbl->tbl_isr_reservations[i].is_reserved == 0)
			{
				// vector is available, add it to the count
				vectors[current_length++] = i;

				// check if required number of vectors was found
				if(current_length == consec_count)
				{
					break;
				}
			}
			else
			{
				// vector is not available, reset counter before moving on
				*vectors = 0;
				current_base = 0;
				current_length = 0;
			}
		}
		else
		{
			// check if current one is available
			if(tbl->tbl_isr_reservations[i].is_reserved == 0)
			{
				// get it and move on
				current_base = i;
				vectors[current_length++] = i;

				// check if required number of vectors was found
				if(current_length == consec_count)
				{
					break;
				}
			}
		}
	}

	// allocate the vectors if they were available
	if(*vectors != 0)
	{
		for(i = 0; i < consec_count; i++)
		{
			tbl->tbl_isr_reservations[vectors[i]].is_reserved = 1;
			tbl->tbl_isr_reservations[vectors[i]].owner_id = 0;
			uint64_t k = GetCPUTimeStamp();
			tbl->tbl_isr_reservations[vectors[i]].key = k;
			keys[i] = k;
		}
	}

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// check if vectors were allocated
	if(*vectors == 0)
	{
		return IRQ_VEC_NOT_AVAILABLE;
	}

	// finished, return success
	return IRQ_SUCCESS;
}


uint32_t	__KLIB__ISRFree(ISR_VECTOR vector, uint64_t key)
{
	// check vector is valid
	if(vector > 255 || vector < 32)
	{
		return IRQ_BAD_VECTOR;
	}

	// check vector is reserved and reservation key is valid
	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check vector is reserved
	if(!(tbl->tbl_isr_reservations[vector].is_reserved))
	{
		// vector is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_VEC_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_isr_reservations[vector].key != key)
	{
		// vector reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// check if the system owns this vector
	if(tbl->tbl_isr_reservations[vector].owner_id == 0xffffffff)
	{
		// vector is owned by the system
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_SYSTEM_OWNED;
	}

	// vector is reserved, it doesn't belong to the system and key is valid. continue
	tbl->tbl_isr_reservations[vector].is_reserved = 0;
	tbl->tbl_isr_reservations[vector].key = 0;

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// finished, return success
	return IRQ_SUCCESS;
}

// external targets and isrs heads
extern uint64_t SYSISR_ISRS;
extern uint64_t SYSISR_TARGETS;

// finds a free ISR with the given IRQ priority and enables it. Returns the vector of the ISR.
uint32_t	__KLIB__ISRRegister(ISR_VECTOR vector, uint64_t key, void (*isr)(uint32_t), uint8_t cpl, uint8_t gate_type, uint8_t stack)
{
	// array of ISRs and targets
	uint64_t *isrs = &SYSISR_ISRS;
	uint64_t *targets = &SYSISR_TARGETS;

	// check vector, CPL, gate, stack and priority
	if(vector > 255 || vector < 32)
	{
		return IRQ_BAD_VECTOR;
	}
	if((cpl != 0) && (cpl != 3))
	{
		return IRQ_BAD_CPL_NUMBER;
	}
	if((gate_type != IRQ_GATE_TRAP) && (gate_type != IRQ_GATE_INTERRUPT))
	{
		return IRQ_BAD_GATE_NUMBER;
	}
	if(stack > 7)
	{
		return IRQ_BAD_STACK_NUMBER;
	}

	// get isr address as int
	uint64_t address = isrs[vector];//(uint64_t)isr;

	// save the address of the target if it is valid
	uint64_t target = (uint64_t)isr;
	if(target < 0x100000000 || target >= 0x120000000)
	{
		return IRQ_BAD_ISR_ADDRESS;
	}

	targets[vector] = target;

	// check vector is reserved and reservation key is valid
	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check vector is reserved
	if(!(tbl->tbl_isr_reservations[vector].is_reserved))
	{
		// vector is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_VEC_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_isr_reservations[vector].key != key)
	{
		// vector reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// get base pointer of system IDT
	IDT64 *idt = (IDT64*)IDT_KOFFSET;

	// get lock on IDT
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&idt_lock, CPU);

	// fill in idt entry
	idt[vector].target_selector = 0x8;
	idt[vector].DPL = cpl;
	idt[vector].type = gate_type;
	idt[vector].zero = 0;
	idt[vector].IST_selector = stack;
	idt[vector].reserved_1 = 0;
	idt[vector].reserved_2 = 0;
	idt[vector].target_offset_low = address & 0xffff;
	idt[vector].target_offset_mid = (address >> 16) & 0xffff;
	idt[vector].target_offset_high = (address >> 32) & 0xffffffff;

	// mark it as present
	idt[vector].present = 1;

	// release lock on IDT
	BakeryLeave(&idt_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// return success
	return IRQ_SUCCESS;
}

uint32_t	__KLIB__ISRUnregister(ISR_VECTOR vector, uint64_t key)
{
	// check vector is valid
	if(vector > 255 || vector < 32)
	{
		return IRQ_BAD_VECTOR;
	}

	// check vector is reserved and reservation key is valid
	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check vector is reserved
	if(!(tbl->tbl_isr_reservations[vector].is_reserved))
	{
		// vector is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_VEC_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_isr_reservations[vector].key != key)
	{
		// vector reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// get base pointer of system IDT
	IDT64 *idt = (IDT64*)IDT_KOFFSET;

	// get lock on IDT
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&idt_lock, CPU);

	// mark vector as not present
	idt[vector].present = 0;

	// release lock on IDT
	BakeryLeave(&idt_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// return success
	return IRQ_SUCCESS;
}

uint32_t	__KLIB__GetGSIFromISA(uint32_t ISA_vector, uint32_t *GSI, uint32_t *polarity, uint32_t *trigger_mode)
{
	// get pointer to the system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// check isa vector is valid
	if((ISA_vector < 0) || (ISA_vector > 15))
	{
		return IRQ_BAD_ISA_VECTOR;
	}

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check ISA-GSA map
	*GSI = tbl->tbl_isa_gsi[ISA_vector].GSI;
	*polarity = tbl->tbl_isa_gsi[ISA_vector].polarity;
	*trigger_mode = tbl->tbl_isa_gsi[ISA_vector].trigger_mode;

	// release lock
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	if(*GSI == -1)
	{
		return IRQ_ISA_NOT_MAPPED;
	}

	return IRQ_SUCCESS;
}

uint32_t	__KLIB__IOAPIC_mask_IRQ(uint32_t GSI, uint64_t key)
{
	// check GSI is a valid number (0 - 255)
	if(GSI > 255)
	{
		return IRQ_BAD_GSI;
	}

	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check GSI is reserved
	if(!(tbl->tbl_gsi_reservations[GSI].is_reserved))
	{
		// GSI is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_gsi_reservations[GSI].key != key)
	{
		// GSI reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// check if routing information is available for this GSI
	if(tbl->tbl_gsi[GSI].IOAPIC_ID == -1)
	{
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_MAPPED;
	}

	// get which IOAPIC and which entry number to use
	uint32_t IOAPIC = tbl->tbl_gsi[GSI].IOAPIC_ID;
	uint32_t pin = tbl->tbl_gsi[GSI].IOAPIC_input_number;

	// get base address for the IOAPIC
	volatile uint32_t *IOAPIC_BASE = (uint32_t*)(uint64_t)tbl->tbl_ioapic[IOAPIC].IOAPIC_address;

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// get lock on apic
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&ioapic_lock, CPU);

	// first read in original data
	__asm__ __volatile__("":::"memory");
	IOAPIC_BASE[0] = 0x10 + (pin * 2);
	__asm__ __volatile__("lfence":::"memory");
	uint32_t reg_lower = IOAPIC_BASE[4];
	__asm__ __volatile__("lfence":::"memory");
	IOAPIC_BASE[0] = 0x11 + (pin * 2);
	__asm__ __volatile__("lfence":::"memory");
	uint32_t reg_upper = IOAPIC_BASE[4];
	__asm__ __volatile__("lfence":::"memory");

	// set the masked bit
	reg_lower = reg_lower | (1 << 16);

	// write it back to the ioapic
	__asm__ __volatile__("":::"memory");
	IOAPIC_BASE[0] = 0x10 + (pin * 2);
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[4] = reg_lower;
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[0] = 0x11 + (pin * 2);
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[4] = reg_upper;
	__asm__ __volatile__("sfence":::"memory");

	// release lock
	BakeryLeave(&ioapic_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// return success
	return IRQ_SUCCESS;
}

uint32_t	__KLIB__IOAPIC_unmask_IRQ(uint32_t GSI, uint64_t key)
{
	// check GSI is a valid number (0 - 255)
	if(GSI > 255)
	{
		return IRQ_BAD_GSI;
	}

	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check GSI is reserved
	if(!(tbl->tbl_gsi_reservations[GSI].is_reserved))
	{
		// GSI is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_gsi_reservations[GSI].key != key)
	{
		// GSI reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// check if routing information is available for this GSI
	if(tbl->tbl_gsi[GSI].IOAPIC_ID == -1)
	{
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_MAPPED;
	}

	// get which IOAPIC and which entry number to use
	uint32_t IOAPIC = tbl->tbl_gsi[GSI].IOAPIC_ID;
	uint32_t pin = tbl->tbl_gsi[GSI].IOAPIC_input_number;

	// get base address for the IOAPIC
	volatile uint32_t *IOAPIC_BASE = (uint32_t*)(uint64_t)tbl->tbl_ioapic[IOAPIC].IOAPIC_address;

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// get lock on apic
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&ioapic_lock, CPU);

	// first read in original data
	__asm__ __volatile__("":::"memory");
	IOAPIC_BASE[0] = 0x10 + (pin * 2);
	__asm__ __volatile__("lfence":::"memory");
	uint32_t reg_lower = IOAPIC_BASE[4];
	__asm__ __volatile__("lfence":::"memory");
	IOAPIC_BASE[0] = 0x11 + (pin * 2);
	__asm__ __volatile__("lfence":::"memory");
	uint32_t reg_upper = IOAPIC_BASE[4];
	__asm__ __volatile__("lfence":::"memory");

	// clear the masked bit
	uint32_t mask = ~(1 << 16);
	reg_lower = reg_lower & mask;

	// write it back to the ioapic
	__asm__ __volatile__("":::"memory");
	IOAPIC_BASE[0] = 0x10 + (pin * 2);
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[4] = reg_lower;
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[0] = 0x11 + (pin * 2);
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[4] = reg_upper;
	__asm__ __volatile__("sfence":::"memory");

	// release lock
	BakeryLeave(&ioapic_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// return success
	return IRQ_SUCCESS;
}


uint32_t	__KLIB__GSIReserve(uint32_t GSI, uint64_t *key)
{
	// check if GSI is valid
	if(GSI > 255)
	{
		return IRQ_BAD_GSI;
	}

	// first if GSI is not reserved and it's not owned by the system
	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check GSI is not reserved
	if(tbl->tbl_gsi_reservations[GSI].is_reserved)
	{
		// GSI is reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_AVAILABLE;
	}

	// check if system owns it
	if(tbl->tbl_gsi_reservations[GSI].owner_id == 0xffffffff)
	{
		// GSI reservation is owned by system
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_SYSTEM_OWNED;
	}

	// then set the information
	*key = GetCPUTimeStamp();
	tbl->tbl_gsi_reservations[GSI].is_reserved = 1;
	tbl->tbl_gsi_reservations[GSI].key = *key;

	// release lock
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// finished, return success
	return IRQ_SUCCESS;
}


uint32_t	__KLIB__GSIFree(uint32_t GSI, uint64_t key)
{
	// check if GSI is valid
	if(GSI > 255)
	{
		return IRQ_BAD_GSI;
	}

	// check if GSI is reserved, key is correct and it's not owned by the system
	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check GSI is reserved
	if(!(tbl->tbl_gsi_reservations[GSI].is_reserved))
	{
		// GSI is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_gsi_reservations[GSI].key != key)
	{
		// GSI reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// check if system owns it
	if(tbl->tbl_gsi_reservations[GSI].owner_id == 0xffffffff)
	{
		// GSI reservation is owned by system
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_SYSTEM_OWNED;
	}

	// then unreserve the GSI
	tbl->tbl_gsi_reservations[GSI].key = 0;
	tbl->tbl_gsi_reservations[GSI].is_reserved = 0;

	// release lock
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// finished, return success
	return IRQ_SUCCESS;
}


uint32_t	__KLIB__IOAPIC_get_IRQ_reg_dump(uint32_t GSI, uint64_t key, uint64_t *buffer)
{
	// check GSI is a valid number (0 - 255)
	if(GSI > 255)
	{
		return IRQ_BAD_GSI;
	}

	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check GSI is reserved
	if(!(tbl->tbl_gsi_reservations[GSI].is_reserved))
	{
		// GSI is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_gsi_reservations[GSI].key != key)
	{
		// GSI reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// check if routing information is available for this GSI
	if(tbl->tbl_gsi[GSI].IOAPIC_ID == -1)
	{
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_MAPPED;
	}

	// get which IOAPIC and which entry number to use
	uint32_t IOAPIC = tbl->tbl_gsi[GSI].IOAPIC_ID;
	uint32_t pin = tbl->tbl_gsi[GSI].IOAPIC_input_number;

	// get base address for the IOAPIC
	volatile uint32_t *IOAPIC_BASE = (uint32_t*)(uint64_t)tbl->tbl_ioapic[IOAPIC].IOAPIC_address;

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// get lock on apic
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&ioapic_lock, CPU);

	// first read in original data
	__asm__ __volatile__("":::"memory");
	IOAPIC_BASE[0] = 0x10 + (pin * 2);
	__asm__ __volatile__("lfence":::"memory");
	uint32_t reg_lower = IOAPIC_BASE[4];
	__asm__ __volatile__("lfence":::"memory");
	IOAPIC_BASE[0] = 0x11 + (pin * 2);
	__asm__ __volatile__("lfence":::"memory");
	uint32_t reg_upper = IOAPIC_BASE[4];
	__asm__ __volatile__("lfence":::"memory");

	// release lock
	BakeryLeave(&ioapic_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// put the data into the buffer
	uint64_t b = reg_upper;
	b = (b << 32) + reg_lower;
	*buffer = b;

	// return success
	return IRQ_SUCCESS;
}

uint32_t	__KLIB__IOAPIC_manual_reg_write(uint32_t GSI, uint64_t key, ISR_VECTOR vector, uint32_t polarity, uint32_t trigger_mode)
{
	// check GSI is a valid number (0 - 255)
	if(GSI > 255)
	{
		return IRQ_BAD_GSI;
	}

	// get pointer to system config table
	TBL_SYSCONFIG *tbl = (TBL_SYSCONFIG*)SYS_KOFFSET;

	// get lock on system config table
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&sysconfig_lock, CPU);

	// check GSI is reserved
	if(!(tbl->tbl_gsi_reservations[GSI].is_reserved))
	{
		// GSI is not reserved
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_RESERVED;
	}

	// check key is correct
	if(tbl->tbl_gsi_reservations[GSI].key != key)
	{
		// GSI reservation key is incorrect
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_KEY_INVALID;
	}

	// check if routing information is available for this GSI
	if(tbl->tbl_gsi[GSI].IOAPIC_ID == -1)
	{
		// release lock and return
		BakeryLeave(&sysconfig_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");

		return IRQ_GSI_NOT_MAPPED;
	}

	// get which IOAPIC and which entry number to use
	uint32_t IOAPIC = tbl->tbl_gsi[GSI].IOAPIC_ID;
	uint32_t pin = tbl->tbl_gsi[GSI].IOAPIC_input_number;
	uint32_t APIC = tbl->tbl_cpu[0].APIC_ID;

	// get base address for the IOAPIC
	volatile uint32_t *IOAPIC_BASE = (uint32_t*)(uint64_t)tbl->tbl_ioapic[IOAPIC].IOAPIC_address;

	// release lock on table
	BakeryLeave(&sysconfig_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// get lock on apic
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&ioapic_lock, CPU);

	// generate the register values
	uint32_t reg_lower = (vector & 0xff) | ((~polarity & 1) << 13) | ((~trigger_mode & 1) << 15);
	uint32_t reg_upper = (APIC & 0xff) << 24;

	// write the buffer to the ioapic
	__asm__ __volatile__("":::"memory");
	IOAPIC_BASE[0] = 0x10 + (pin * 2);
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[4] = reg_lower;
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[0] = 0x11 + (pin * 2);
	__asm__ __volatile__("sfence":::"memory");
	IOAPIC_BASE[4] = reg_upper;
	__asm__ __volatile__("sfence":::"memory");

	// release lock
	BakeryLeave(&ioapic_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// return success
	return IRQ_SUCCESS;
}
